//Program by:
//Ben Schmidt - bjs778 - 11054018
//Mike Nimeck - mfn028 - 11053045
//Jeff Huang - jlh105 - 11050226
//
//Fully implemented by hand, but OpenGL SuperBible example
//used heavily as reference

//includes
#include <stdlib.h>
#include <time.h>

#include <string>

#include <GLTools.h>
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include "glUtils.h"

#include "main.h"
#include "flocking.h"
#include "fireworks.h"
#include "gravity.h"
#include "controller.h"
#include "utils.h"

using namespace std;

GLuint render_program;
GLuint base_size_loc;
GLuint depth_divisor_loc;
GLint base_size = 5;
float depth_divisor = 50.0f;
GLint mvp_loc;
int SHADER_INDEX;

GLuint update_vao[2];
GLuint particle_position[2];
GLuint particle_velocity[2];
GLuint particle_color[2];
GLuint particle_colorVelocity[2];
GLuint particle_mass[2];

GLuint firework_colors;

GLFrame view_frame;
GLFrustum view_frustum;

GLuint numParticles;

float trail_length;

ParticleType particle_type;

GLuint GRAVITY_PARTICLES = 50;
GLuint FLOCK_SIZE = 5000;
GLuint FIREWORK_PARTICLES = 5000;

int WINDOW_WIDTH = 800;
int WINDOW_HEIGHT = 600;

float MAX_INITIAL_GRAVITY_SPEED = 10.0f;

float timestep_mult = 50.0f;
float timestep_mult_step = 5.0f;

float rule1_weight = 0.17f;
float rule2_weight = 0.01f;
float rule3_weight = 0.75f;
float rule_speed = 0.01f;

float colour_rule1_weight = 0.17f;
float colour_rule2_weight = 0.01f;
float colour_rule3_weight = 0.75f;
float colour_rule_speed = 0.01f;

float closest_allowed_distance = 200.0f;
float closest_allowed_distance_speed = 10.0f;

float closest_allowed_colour = 0.025f;
float closest_allowed_colour_speed = 0.001f;

float firework_fuzziness_increment = 0.05f;

GLuint frame_index = 0;


//handles changing the size of the window
void change_size(int w, int h){
    //prevent divide by zero
    if (h == 0){
	h = 1;
    }
    
    WINDOW_WIDTH = w;
    WINDOW_HEIGHT = h;
    
    //set viewport to window dimensions
    glViewport(0, 0, w, h);
    
    view_frustum.SetPerspective(FIELD_OF_VIEW, 
				float(w)/float(h), 1.0f, 5000.0f);
    
    projection_mat.LoadMatrix(view_frustum.GetProjectionMatrix());
    transform_pipeline.SetMatrixStacks(modelview_mat, projection_mat);
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClear(GL_ACCUM_BUFFER_BIT);  
}

//generate initial positions and velocities
void init_particles(float* positions, 
		    unsigned int num_positions, 
		    float scale, 
		    const float *bias)
{
    int i;
    float* p = positions;
    
    //NOTE: *p++ uses *p for the expression, 
    // then updates p to the next element.
    if (bias)
    {
	for (i = 0; i < num_positions; i++)
	{
	    *p++ = random_float() * scale + bias[0];
	    *p++ = random_float() * scale + bias[1];
	    *p++ = random_float() * scale + bias[2];
	}
    }
    else
    {
	for (i = 0; i < num_positions; i++)
	{
	    *p++ = random_float() * scale;
	    *p++ = random_float() * scale;
	    *p++ = random_float() * scale;
	}
    }
}

//initialize the particle system
void init()
{
	int i;
	
	if (particle_type == FLOCKING){
	    SHADER_INDEX = COLORFLOCK_SHADER;
	}
	else if (particle_type == FIREWORKS){
	    SHADER_INDEX = FIREWORKS_SHADER;
	}
	else if (particle_type == GRAVITY){
	    SHADER_INDEX = GRAVMASS_SHADER;
	}
	
	//set the trail length
	trail_length = 0.0f;
	
	//set clear colors
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClearAccum(0.0f, 0.0f, 0.0f, 0.0f);
	
	//some video cards need this
	glEnable(GL_POINT_SPRITE);

	//seed the random number generator
	srand((unsigned)time(0));
	
	//clear buffers so that previous program doesn't interfere
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClear(GL_ACCUM_BUFFER_BIT);
	
	//set the center of our projection away from the system, 
	//so that you can see it
	view_frame.MoveForward(DISTANCE_FROM_SYSTEM);
	
	//create the vertex array objects, used for updating and rendering
	glGenVertexArrays(2, update_vao);
	glGenVertexArrays(2, render_vao);
	
	//create some buffers
	//position and velocity get two each, and airplane geometry gets one
	glGenBuffers(2, particle_position);
	glGenBuffers(2, particle_velocity);
	glGenBuffers(1, &particle_geometry);
	
	if (particle_type == FLOCKING)
	{
		glGenBuffers(2, particle_color);
		glGenBuffers(2, particle_colorVelocity);
	}
	else if (particle_type == FIREWORKS)
	{
		glGenBuffers(1, &firework_colors);
	}
	else if (particle_type == GRAVITY)
	{
		glGenBuffers(2, particle_mass);
	}

	if (particle_type == FLOCKING)
	{
		numParticles = FLOCK_SIZE;
		initFlocking();

	}
	else if (particle_type == FIREWORKS)
	{
		numParticles = FIREWORK_PARTICLES * MAX_NUM_FIREWORKS;
		initFireworks();
	}
	else if (particle_type == GRAVITY)
	{
		numParticles = GRAVITY_PARTICLES;
		initGravity();

	}
	
	//
	//set up second update VAO
	//
	
	//positions
	glBindVertexArray(update_vao[1]);
	glBindBuffer(GL_ARRAY_BUFFER, particle_position[1]);
	glBufferData(GL_ARRAY_BUFFER, numParticles * 3 * sizeof(float), NULL, GL_DYNAMIC_COPY);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(0);

	//velocities
	glBindBuffer(GL_ARRAY_BUFFER, particle_velocity[1]);
	glBufferData(GL_ARRAY_BUFFER, numParticles * 3 * sizeof(float), NULL, GL_DYNAMIC_COPY);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(1);
    
	if (particle_type == FLOCKING)
	{
		//colors - update vao
		glBindBuffer(GL_ARRAY_BUFFER, particle_color[1]);
		glBufferData(GL_ARRAY_BUFFER, 
			numParticles * 3 * sizeof(float), NULL, GL_DYNAMIC_COPY);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(2);
		
		//colorVelocities - update vao
		glBindBuffer(GL_ARRAY_BUFFER, particle_colorVelocity[1]);
		glBufferData(GL_ARRAY_BUFFER, 
			numParticles * 3 * sizeof(float), NULL, GL_DYNAMIC_COPY);
		glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(3);
	}
	else if (particle_type == GRAVITY)
	{
		//masses - update vao
		glBindBuffer(GL_ARRAY_BUFFER, particle_mass[1]);
		glBufferData(GL_ARRAY_BUFFER, 
			numParticles * 3 * sizeof(float), NULL, GL_DYNAMIC_COPY);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(2);
	}
	//
	//set up render VAOs
	//

	//do this once for each render VAO
	for (i = 0; i < 2; i++)
	{
		//positions
		glBindVertexArray(render_vao[i]);
		glBindBuffer(GL_ARRAY_BUFFER, particle_position[i]);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(0);
		glVertexAttribDivisor(0, 1);
		
		//velocities
		glBindBuffer(GL_ARRAY_BUFFER, particle_velocity[i]);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(1);
		glVertexAttribDivisor(1, 1);
		
		//geometry
		glBindBuffer(GL_ARRAY_BUFFER, particle_geometry);
		glVertexAttribPointer(10, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		glEnableVertexAttribArray(10);
		
		if (particle_type == FLOCKING)
		{
		    glBindBuffer(GL_ARRAY_BUFFER, particle_color[i]);
		    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		    glEnableVertexAttribArray(2);
		    glVertexAttribDivisor(2, 1);
		    
		    glBindBuffer(GL_ARRAY_BUFFER, particle_colorVelocity[i]);
		    glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		    glEnableVertexAttribArray(3);
		    glVertexAttribDivisor(3, 1);
		}
		if (particle_type == FIREWORKS)
		{
		    glBindBuffer(GL_ARRAY_BUFFER, firework_colors);
		    glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		    glEnableVertexAttribArray(5);
		    glVertexAttribDivisor(5, 1);
		    
		}
		else if (particle_type == GRAVITY)
		{
		    glBindBuffer(GL_ARRAY_BUFFER, particle_mass[i]);
		    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		    glEnableVertexAttribArray(2);
		    glVertexAttribDivisor(2, 1);
		}
	}
	
	//only allocate static geometry buffer once
	glBindBuffer(GL_ARRAY_BUFFER, particle_geometry);
	glBufferData(GL_ARRAY_BUFFER, PARTICLE_MODEL_SIZE, PARTICLE_MODEL, GL_STATIC_DRAW);

	//set up position and velocity texture buffer objects
	glGenTextures(2, position_tbo);
	glGenTextures(2, velocity_tbo);
	
	if (particle_type == FLOCKING)
	{
		glGenTextures(2, color_tbo);
		glGenTextures(2, colorVelocity_tbo);
	}
	else if (particle_type == GRAVITY)
	{
		//masses - update
		glGenTextures(2, mass_tbo);
	}
	
	//finish up with VAOs
	glBindVertexArray(0);
    
	//set up TBOs
	for (i = 0; i < 2; i++)
	{
		glBindTexture(GL_TEXTURE_BUFFER, position_tbo[i]);
		glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, particle_position[i]);
		
		glBindTexture(GL_TEXTURE_BUFFER, velocity_tbo[i]);
		glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, particle_velocity[i]);
		
		if (particle_type == FLOCKING)
		{
			//color - update vao
			glBindTexture(GL_TEXTURE_BUFFER, color_tbo[i]);
			glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, particle_color[i]);
			
			//colorVelocity - update vao
			glBindTexture(GL_TEXTURE_BUFFER, colorVelocity_tbo[i]);
			glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, particle_colorVelocity[i]);
		}
		else if (particle_type == GRAVITY)
		{
			//masses - update vao
			glBindTexture(GL_TEXTURE_BUFFER, mass_tbo[i]);
			glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, particle_mass[i]);
		}
	}

	//
	//create the update program
	//
	update_program = glCreateProgram();
	
	//load and compile the update program's vertex shader
	update_vs = glCreateShader(GL_VERTEX_SHADER);
	gltLoadShaderFile(particle_files[particle_type], update_vs);
	glCompileShader(update_vs);
	glAttachShader(update_program, update_vs);

	//set up transform feedback varyings
	if (particle_type == FLOCKING){
	    glTransformFeedbackVaryings(
		update_program, 
		4, 
		tf_flocking_outputs, 
		GL_SEPARATE_ATTRIBS);
	}
	else if (particle_type == FIREWORKS){
	    glTransformFeedbackVaryings(
		update_program, 
		2, 
		tf_fireworks_outputs, 
		GL_SEPARATE_ATTRIBS);
	}
	else if (particle_type == GRAVITY){
	    glTransformFeedbackVaryings(
		update_program, 
		3, 
		tf_gravity_outputs, 
		GL_SEPARATE_ATTRIBS);
	}

	//link the update program
	glLinkProgram(update_program);
	glUseProgram(update_program);

	//set up the sampler uniforms (you can't initialize defaults in the shader)
	if (particle_type == FLOCKING)
	{
		glUniform1i(glGetUniformLocation(update_program, "tex_positions"), 0);
		glUniform1i(glGetUniformLocation(update_program, "tex_velocities"), 1);
		glUniform1i(glGetUniformLocation(update_program, "tex_colors"), 2);
		glUniform1i(glGetUniformLocation(update_program, "tex_colorVelocities"), 3);
	}
	else if (particle_type == GRAVITY)
	{
	    glUniform1i(glGetUniformLocation(update_program, "tex_positions"), 0);
	    glUniform1i(glGetUniformLocation(update_program, "tex_velocities"), 1);
	    glUniform1i(glGetUniformLocation(update_program, "tex_mass"), 2);  
	}

	//set the system size
	glUniform1i(glGetUniformLocation(update_program, "num_particles"), numParticles);

	
	//get the location of the timestep uniforms
	timestep_loc = glGetUniformLocation(update_program, "timestep");
    
	if (particle_type == FLOCKING)
	{
		rule1_loc = 
			glGetUniformLocation(update_program, "rule1_weight");
		rule2_loc = 
			glGetUniformLocation(update_program, "rule2_weight");
		rule3_loc = 
			glGetUniformLocation(update_program, "rule3_weight");
		colour_rule1_loc = 
			glGetUniformLocation(update_program, "colorRule1_weight");
		colour_rule2_loc = 
			glGetUniformLocation(update_program, "colorRule2_weight");
		colour_rule3_loc = 
			glGetUniformLocation(update_program, "colorRule2_weight");
		closest_dist_loc = 
			glGetUniformLocation(update_program, "closest_allowed_dist");
		closest_colour_loc = 
			glGetUniformLocation(update_program,
					     "closest_allowed_color");

	    goal_loc = glGetUniformLocation(update_program, "goal");
	    goalColor_loc = glGetUniformLocation(update_program, "colorGoal");
	}
	else if (particle_type == GRAVITY)
	{
	    grav_const_loc = glGetUniformLocation(update_program, "gravitational_constant");
	    mass_loc = glGetUniformLocation(update_program, "mass");
	    toggle_loc = glGetUniformLocation(update_program, "center_toggle");
	}
	
	//done with update program
	glUseProgram(0);
	
	loadShaderProgram();
}
    
//render the scene
void render_scene(void){
	//set up static state
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);

	//update the particles
	glUseProgram(update_program);
	
	//update timestep and, to make things interesting, the goal location
	float t = stopwatch.GetElapsedSeconds();
	if (particle_type == FLOCKING)
	{
		glUniform1f(rule1_loc, rule1_weight);
		glUniform1f(rule2_loc, rule2_weight);
		glUniform1f(rule3_loc, rule3_weight);
		glUniform1f(colour_rule1_loc, colour_rule1_weight);
		glUniform1f(colour_rule2_loc, colour_rule2_weight);
		glUniform1f(colour_rule3_loc, colour_rule3_weight);
		glUniform1f(closest_dist_loc, closest_allowed_distance);
		glUniform1f(closest_colour_loc, closest_allowed_colour);
		update_idle_flocking(goal_position, goal_color, t);
		glUniform3fv(goal_loc, 1, goal_position);
		glUniform3fv(goalColor_loc, 1, goal_color);
	}
	else if (particle_type == GRAVITY)
	{
		glUniform1f(mass_loc, GRAVITY_MASS);
		glUniform1f(grav_const_loc, GRAVITATIONAL_CONSTANT);
		glUniform1f(toggle_loc, gravity_center_toggle);
	}

	glUniform1f(timestep_loc, (t - last_update_time) * (timestep_mult));
isGLError();
	last_update_time = t;
	//buffer swapping code
	//do different things depending on if we're rendering an even or odd frame
	if (frame_index & 1)
	{
		//read from second set of buffers
		if (particle_type == FLOCKING)
		{
		    glActiveTexture(GL_TEXTURE2);
		    glBindTexture(GL_TEXTURE_BUFFER, color_tbo[1]);
		    
		    glActiveTexture(GL_TEXTURE3);
		    glBindTexture(GL_TEXTURE_BUFFER, colorVelocity_tbo[1]);
		}
		else if (particle_type == GRAVITY)
		{
			glActiveTexture(GL_TEXTURE2);
			glBindTexture(GL_TEXTURE_BUFFER, mass_tbo[1]);
		}
		
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_BUFFER, velocity_tbo[1]);
		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_BUFFER, position_tbo[1]);
			
		glBindVertexArray(update_vao[1]);
			
		//write to first set of buffers
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
				particle_position[0]);
		
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1,
				particle_velocity[0]);

		if (particle_type == FLOCKING)
		{
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2,
					particle_color[0]);
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 3,
					particle_colorVelocity[0]);
		}
		else if (particle_type == GRAVITY)
		{
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2,
					particle_mass[0]);
		}
	}
	else
	{
		if (particle_type == FLOCKING)
		{
		    glActiveTexture(GL_TEXTURE2);
		    glBindTexture(GL_TEXTURE_BUFFER, color_tbo[0]);
		    
		    glActiveTexture(GL_TEXTURE3);
		    glBindTexture(GL_TEXTURE_BUFFER, colorVelocity_tbo[0]);
		}
		else if (particle_type == GRAVITY)
		{
			//read from first set of buffers
			glActiveTexture(GL_TEXTURE2);
			glBindTexture(GL_TEXTURE_BUFFER, mass_tbo[0]);
		}

		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_BUFFER, velocity_tbo[0]);
		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_BUFFER, position_tbo[0]);
			
		glBindVertexArray(update_vao[0]);
			
		//write to second set of buffers
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
				particle_position[1]);
			
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1,
				particle_velocity[1]);

		if (particle_type == FLOCKING)
		{
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2,
					particle_color[1]);
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 3,
					particle_colorVelocity[1]);
		}
		else if (particle_type == GRAVITY)
		{
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2,
					particle_mass[1]);
		}
	}

	//turn off rasterization for the update pass
	glEnable(GL_RASTERIZER_DISCARD);

	//do the work
	glBeginTransformFeedback(GL_POINTS);
	glDrawArrays(GL_POINTS, 0, numParticles);
	glEndTransformFeedback();

	//turn rasterization back on
	glDisable(GL_RASTERIZER_DISCARD);

	glDisable(GL_TEXTURE_2D);

	//clear the framebuffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	// get the accumulation buffer (multiplied by some constant)
	glAccum(GL_RETURN, 1.0f);

	//set up point rendering
	if (GEOMETRY_SHADERS[SHADER_INDEX] == NULL){
	    glEnable(GL_PROGRAM_POINT_SIZE);
	}

	// clear the accumulation buffer
	glClear(GL_ACCUM_BUFFER_BIT);
		
	//set things up to render
	glUseProgram(render_program);
	
	//set projection matrix
	modelview_mat.PushMatrix(view_frame);

	glUniformMatrix4fv(mvp_loc, 1, GL_FALSE,
			transform_pipeline.GetModelViewProjectionMatrix());

	modelview_mat.PopMatrix();

	//bind different vertex arrays depending on if it's an even or odd frame
	if (frame_index & 1)
	{
		glBindVertexArray(render_vao[1]);
	}
	else{
		glBindVertexArray(render_vao[0]);
	}    

	//draw
	if (GEOMETRY_SHADERS[SHADER_INDEX] != NULL)
	{
	    glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, PARTICLE_MODEL_NUM_POINTS, numParticles);
	}
	else
	{
	    if (SHADER_INDEX != LSD_SHADER){
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(GL_GREATER, 0.3f);
	    }
	    
	    glDrawArraysInstanced(GL_POINTS, 0, 1, numParticles);
	    
	    if (SHADER_INDEX != LSD_SHADER){
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_BLEND);
	    }
	}
	isGLError();
	
	//put the current scene into the accumulation buffer
	glAccum(GL_ACCUM, trail_length);
	
	//update frame counter
	frame_index++;

	//finish up
	glutSwapBuffers();
	glutPostRedisplay();
}

// modifies the number of particles in the system
void updateSize(int update_size)
{
	if (particle_type == FLOCKING)
	{
		FLOCK_SIZE = update_size;
	}
	else if (particle_type == GRAVITY)
	{
		GRAVITY_PARTICLES = update_size;
	}
	else if (particle_type == FIREWORKS)
	{
		FIREWORK_PARTICLES = update_size;
	}

}


//does any necessary cleanup before the program shuts down
void cleanup(void){
    glDeleteTextures(1, &particle_sprite);
}

//entry point for the program
//calls all necessary initialization functions and sturts glut's main loop
int main(int argc, char* argv[])
{  
	// Initialize variables for program creation
	int choice = 1;
	int particle_input = 1;

	do
	{
		printf("\n"
		"0) Flocking\n"
		"1) Fireworks\n"
		"2) Gravity\n"
		"Which program do you want to run? ");
		scanf("%d", &choice);
	} while (choice < 0 || choice > 2);

	particle_type = (ParticleType)choice;
		
	do
	{
		printf(
		"Enter number of particles: ");
		scanf("%d", &particle_input);
	} while (particle_input < 0);
	
	updateSize(particle_input);
	
	if (particle_type == GRAVITY){
	    do
	    {
		    printf("\n"
		    "Enter max initial speed: ");
		    scanf("%f", &MAX_INITIAL_GRAVITY_SPEED);
	    } while (MAX_INITIAL_GRAVITY_SPEED < 0);   
	}
	
	printControls();
	
	gltSetWorkingDirectory(argv[0]);
	glutInit(&argc, argv);
	glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);
	glutInitContextVersion(3, 3);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutCreateWindow("Particle System");
	glutReshapeFunc(change_size);
	glutKeyboardFunc(keyboard_handler);
	glutSpecialFunc(special_handler);
	glutDisplayFunc(render_scene);
	glutMouseFunc(mouseAction);
	
	GLenum err = glewInit();
	if (GLEW_OK != err){
		//glewInit failed
		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	}
	
	stopwatch.Reset();
	
	init();
	
	glutMainLoop();
	cleanup();
	
	return EXIT_SUCCESS;
}
